/*******************************************************
 * Title: DateUtil.java
 * Description: A utility to handle date related 
 * 				issues.
 * 
 * Copyright 2009. silverduner.com. 
 * All rights reserved.
 *******************************************************/
package com.silverduner.common;

import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.Date;
import java.util.Locale;


/**
 * @author Yuwen Sun
 * @version 1.0
 */
public class DateUtils {
	private static Calendar calendar = Calendar.getInstance();
	
    /**
     * Number of milliseconds in a standard second.
     */
    public static final long MILLIS_PER_SECOND = 1000;
    /**
     * Number of milliseconds in a standard minute.
     */
    public static final long MILLIS_PER_MINUTE = 60 * MILLIS_PER_SECOND;
    /**
     * Number of milliseconds in a standard hour.
     */
    public static final long MILLIS_PER_HOUR = 60 * MILLIS_PER_MINUTE;
    /**
     * Number of milliseconds in a standard day.
     */
    public static final long MILLIS_PER_DAY = 24 * MILLIS_PER_HOUR;
    
	/**
	 * Check, whether the date passed in is the weekend.
	 * 
	 * @param date date to check
	 * @return <code>true</code> if <var>date</var> corresponds to the weekend
	 */
	public static boolean isWeekEnd(Date date){
		synchronized (calendar) {
			calendar.setTime(date);
			if (calendar.get(Calendar.DAY_OF_WEEK) == Calendar.SATURDAY
					|| calendar.get(Calendar.DAY_OF_WEEK) == Calendar.SUNDAY)
				return true;
			else
				return false;
		}
	}

	/**
	 * Check, whether the week passed in is the weekend.
	 * 
	 * @param dayOfWeek day of week in integer @see {@link Calendar#DAY_OF_WEEK}
	 * @return <code>true</code> if <var>date</var> corresponds to the weekend
	 */
	public static boolean isWeekEnd(int dayOfWeek){
		if(dayOfWeek == Calendar.SATURDAY 
				|| dayOfWeek == Calendar.SUNDAY)
			return true;
		else
			return false;
	}

	/**
	 * Check, whether the date passed in is the weekday.
	 * 
	 * @param date date to check
	 * @return <code>true</code> if <var>date</var> corresponds to the weekday
	 */
	public static boolean isWeekday(Date date){
		return !isWeekEnd(date);
	}

	/**
	 * Check, whether the week passed in is the weekday.
	 * 
	 * @param dayOfWeek day of week in integer @see {@link Calendar#DAY_OF_WEEK}
	 * @return <code>true</code> if <var>date</var> corresponds to the weekday
	 */
	public static boolean isWeekday(int dayOfWeek){
		return !isWeekEnd(dayOfWeek);
	}

    /**
     * Check, whether the date passed in is the first day of the month.
     *
     * @param date date to check
     * @return <code>true</code> if <var>date</var> corresponds to the first
     *         day of a month
     * @see Date#getTime() 
     */
	public static boolean isFirstDayOfMonth(Date date){
		int currentMonth;
		int yesterdayMonth;
		synchronized (calendar) {
			calendar.setTime(date);
			currentMonth = calendar.get(Calendar.MONTH);
			calendar.add(Calendar.DAY_OF_MONTH, -1);
			yesterdayMonth = calendar.get(Calendar.MONTH);
		}
		if(currentMonth == yesterdayMonth)
			return false;
		else
			return true;
	}
	
    /**
     * Check, whether the date passed in is the end of month.
     *
     * @param date date to check
     * @return <code>true</code> if <var>date</var> corresponds to the end of month
     * @see Date#getTime() 
     */
	public static boolean isLastDayOfMonth(Date date){
		int currentMonth;
		int tomorrowMonth;
		synchronized (calendar) {
			calendar.setTime(date);
			currentMonth = calendar.get(Calendar.MONTH);
			calendar.add(Calendar.DAY_OF_MONTH, 1);
			tomorrowMonth = calendar.get(Calendar.MONTH);
		}
		if(currentMonth == tomorrowMonth)
			return false;
		else
			return true;
	}


	/**
	 * Format a Date object to a string.
	 * 
	 * @param date date to format
	 * @param pattern the pattern describing the date and time format. @see SimpleDateFormat
	 * @return the formatted time string
	 */
	public static String format(Date date, String pattern){
		SimpleDateFormat formatter = new SimpleDateFormat(pattern);
		return formatter.format(date);
	}

	public static String format(Date date, String pattern, Locale locale){
		SimpleDateFormat formatter = new SimpleDateFormat(pattern,locale);
		return formatter.format(date);
	}

	/**
	 * Parse a time string to a Date object
	 * 
	 * @param source the time string
	 * @param pattern the pattern describing the date and time format. @see SimpleDateFormat
	 * @return the parsed Date object
	 * @throws ParseException if the beginning of the specified string cannot be parsed.
	 */
	public static Date parse(String source, String pattern) throws ParseException{
		SimpleDateFormat formatter = new SimpleDateFormat(pattern);
		return formatter.parse(source);
	}
	
	public static Date parse(String source, String pattern, Locale locale) throws ParseException{
		SimpleDateFormat formatter = new SimpleDateFormat(pattern,locale);
		return formatter.parse(source);
	}
	
	/**
	 * Adds or subtracts the specified day to the given calendar field, based on the calendar's rules.
	 * @see Calendar#add(int, int)
	 * @param date date to add or subtract
	 * @param amount the amount of day to be added.
	 * @return the <var>date</var> + <var>amount</var> days
	 */
	public static Date addDays(Date date,int amount){
		synchronized (calendar) {
			calendar.setTime(date);
			calendar.add(Calendar.DAY_OF_MONTH, amount);
			return calendar.getTime();
		}
	}

	/**
	 * Adds or subtracts the specified month to the given calendar field, based on the calendar's rules.
	 * @see Calendar#add(int, int)
	 * @param date date to add or subtract
	 * @param amount the amount of month to be added.
	 * @return the <var>date</var> + <var>amount</var> months
	 */
	public static Date addMonths(Date date,int amount){
		synchronized (calendar) {
			calendar.setTime(date);
			calendar.add(Calendar.MONTH, amount);
			return calendar.getTime();
		}
	}

	/**
	 * Adds or subtracts the specified year to the given calendar field, based on the calendar's rules.
	 * @see Calendar#add(int, int)
	 * @param date date to add or subtract
	 * @param amount the amount of year to be added.
	 * @return the <var>date</var> + <var>amount</var> years
	 */
	public static Date addYears(Date date,int amount){
		synchronized (calendar) {
			calendar.setTime(date);
			calendar.add(Calendar.YEAR, amount);
			return calendar.getTime();
		}
	}

    /**
     * Get the day after <code>date</code>.
     *
     * @param date Date used in calculating next day
     * @return day after <code>date</code>.
     */
	public static Date getNextDay(Date date){
		return addDays(date, 1);
	}

    /**
     * Get the day before <code>date</code>.
     *
     * @param date Date used in calculating previous week
     * @return day before <code>date</code>.
     */
	public static Date getPreviousDay(Date date){
		return addDays(date, -1);
	}

    /**
     * Get the weekday after <code>date</code>.
     *
     * @param date Date used in calculating next day
     * @return weekday after <code>date</code>.
     */
	public static Date getNextWeekday(Date date){
		synchronized (calendar) {
			calendar.setTime(date);
			int dayOfWeek = calendar.get(Calendar.DAY_OF_WEEK);
			if (dayOfWeek == Calendar.FRIDAY)
				calendar.add(Calendar.DAY_OF_MONTH, 3);
			else if (dayOfWeek == Calendar.SATURDAY)
				calendar.add(Calendar.DAY_OF_MONTH, 2);
			else
				calendar.add(Calendar.DAY_OF_MONTH, 1);
			return calendar.getTime();
		}
	}

    /**
     * Get the weekday before <code>date</code>.
     *
     * @param date Date used in calculating previous week
     * @return weekday before <code>date</code>.
     */
	public static Date getPreviousWeekday(Date date){
		synchronized (calendar) {
			calendar.setTime(date);
			int dayOfWeek = calendar.get(Calendar.DAY_OF_WEEK);
			if (dayOfWeek == Calendar.MONDAY)
				calendar.add(Calendar.DAY_OF_MONTH, -3);
			else if (dayOfWeek == Calendar.SUNDAY)
				calendar.add(Calendar.DAY_OF_MONTH, -2);
			else
				calendar.add(Calendar.DAY_OF_MONTH, -1);
			return calendar.getTime();
		}
	}
	
    /**
     * Get the last day of specific month
     *
     * @param date Date used in calculating month
     * @return last day of month.
     */
	public static Date getLastDayOfMonth(Date date){
		synchronized (calendar) {
			calendar.setTime(date);
			//set to the first day of next month
			calendar.add(Calendar.MONTH, 1);
			calendar.set(Calendar.DAY_OF_MONTH, 1);
			//add back one day
			calendar.add(Calendar.DAY_OF_MONTH, -1);
			return calendar.getTime();
		}
	}

    /**
     * Get the first day of specific month
     *
     * @param date Date used in calculating month
     * @return first day of month.
     */
	public static Date getFirstDayOfMonth(Date date){
		synchronized (calendar) {
			calendar.setTime(date);
			//set to the first day of month
			calendar.set(Calendar.DAY_OF_MONTH, 1);
			return calendar.getTime();
		}
	}

    /**
     * Get the last weekday of specific month
     *
     * @param date Date used in calculating month
     * @return last weekday of month.
     */
	public static Date getLastWeekdayOfMonth(Date date){
		Date firstDayOfNextMonth;
		synchronized (calendar) {
			calendar.setTime(date);
			//set to the first day of next month
			calendar.add(Calendar.MONTH, 1);
			calendar.set(Calendar.DAY_OF_MONTH, 1);
			firstDayOfNextMonth = calendar.getTime();
		}
		return getPreviousWeekday(firstDayOfNextMonth);
	}

    /**
     * Get the first weekday of specific month
     *
     * @param date Date used in calculating month
     * @return first weekday of month.
     */
	public static Date getFirstWeekdayOfMonth(Date date){
			Date firstDayOfMonth;
			boolean isWeekday;
			synchronized (calendar) {
				calendar.setTime(date);
				//set to the first day of month
				calendar.set(Calendar.DAY_OF_MONTH, 1);
				firstDayOfMonth = calendar.getTime();
				isWeekday = isWeekday(calendar.get(Calendar.DAY_OF_WEEK));
			}
			if (isWeekday)
				return firstDayOfMonth;
			else
				return getNextWeekday(firstDayOfMonth);
	}

    /**
     * Returns the day of the week.
     *
     * @param date date
     * @return day of week.
     */
    public static int getDayOfWeek(Date date) {
        synchronized (calendar) {
			calendar.setTime(date);
			return (calendar.get(Calendar.DAY_OF_WEEK));
		}
    }
    
    /**
     * Returns the last millisecond of the specified date.
     *
     * @param date Date to calculate end of day from
     * @return Last millisecond of <code>date</code>
     */
    public static Date endOfDay(Date date) {
        synchronized(calendar) {
            calendar.setTime(date);
            calendar.set(Calendar.HOUR_OF_DAY, 23);
            calendar.set(Calendar.MILLISECOND, 999);
            calendar.set(Calendar.SECOND, 59);
            calendar.set(Calendar.MINUTE, 59);
            return calendar.getTime();
        }
    }


    /**
     * Returns a new Date with the hours, milliseconds, seconds and minutes
     * set to 0.
     *
     * @param date Date used in calculating start of day
     * @return Start of <code>date</code>
     */
    public static Date startOfDay(Date date) {
        synchronized(calendar) {
            calendar.setTime(date);
            calendar.set(Calendar.HOUR_OF_DAY, 0);
            calendar.set(Calendar.MILLISECOND, 0);
            calendar.set(Calendar.SECOND, 0);
            calendar.set(Calendar.MINUTE, 0);
            return calendar.getTime();
        }
    }
}
